函数
函数
函数:是组织好的,可重复使用的,用来实现特定功能的代码段。
我们使用过的:input()、print()、str()、int() 等都是 Python 的内置函数
定义函数
def function_name(params):
function_body
return value
其中参数可以省略,返回值也可以省略
函数使用步骤:先定义函数,后调用函数
简单实践:
# 可以直接在参数上设置默认值
def main(name="xiashuo.xyz"):
print(f"{name} main function")
return name + "-----"
# 因为python代码都是顺序执行的,我们调用函数之前,必须要保证这个函数已经定义了才可以,所以,对main函数的定义必须在对main函数的调用之前,否则会报错
# 使用参数的默认值
main()
main("aaa")
print(main("bbb"))
输出:
xiashuo.xyz main function
aaa main function
bbb main function
bbb-----
在 Java 中,定义一个方法,必须在类之中,其次,定义一个方法的时候无法在参数中指定默认值,跟 Python 一样,也可以没有返回值(返回值类型为 void),同时还有可见性的配置(private、public 等),Python 中好像没有可见性的配置
注意,如果变量跟方法同名,则会相互覆盖,谁后声明,谁最后生效。所以,我们定义变量/方法的时候,千万要注意不要跟已经声明的方法/变量同名。不然会造成麻烦
当变量跟方法重名了的时候,Pycharm 一般都会给个提示,要么是标黄,要么是底下有下划线
简单测试如下:
# 注意,如果变量跟方法同名,则会相互覆盖,谁后声明,谁最后生效
# 所以,我们定义变量/方法的时候,千万要注意不要跟已经声明的方法/变量同名
# 不然会造成麻烦
def test_func(aaa, bbb):
print(f"{aaa},{bbb}")
return aaa
test_func = "124546"
print(test_func)
# test_func("sdsd", "sdsd")
my_name = "xiashuo"
def my_name():
print("xiashuo")
# 输出 <function my_name at 0x0000017EBC33A830>
print(my_name)
# 输出 xiashuo
my_name()
输出:
124546
<function my_name at 0x0000017EBC33A830>
xiashuo
添加方法注释
方法体的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
pyCharm中添加方法注释(Docstring format & Live Templates)_pycharm自动添加方法注释_dkjkls的博客-CSDN博客
在方法体的第一行中输入 """
或者 '''
然后回车,自动生成 reStructuredText
风格的注释,
注释的风格可以在
File -> Settings -> Tools -> Python Integrated Tools -> Docstrings -> Docstring format
中切换
实践如下:
def test_doc(aaa,bbb):
"""
测试输出
:param aaa:
:param bbb:
:return:
"""
print(f"{aaa},{bbb}")
return aaa
test_doc(12, "sdsd")
将鼠标放到 test_doc
上,会有提示:

pass 关键字
在 Python 中有时候会看到一个 def 函数:
def sample(n_samples):
pass
该处的 pass
便是占据一个位置,因为如果定义一个空函数程序会报错,当你没有想好函数的内容是可以用 pass 填充,使程序可以正常运行。
以 pass 作为方法体的函数,有点类似于 Java 中的抽象类
方法参数
调用方法时可使用的正式参数类型
-
位置参数(positional argument)
位置参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。
-
关键字参数
函数调用时通过“键=值”形式传递参数。使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
关键字参数只能在位置参数的后面
-
默认参数
相关实践请看
默认值
小节 -
不定长参数
不定长参数也叫可变参数. 用于不确定调用的时候会传递多少个参数 (不传参也可以) 的场景,类型有,位置传递的不定长参数(以
*
开头的参数)和关键字传递的不定长参数(以**
开头的参数),相关实践请看参数名以*开头
小节和参数名以**开头
小节。
关于关键字参数,简单实践如下:
def info(age,name):
return print(f"我的名字是:{name},年龄是:{age}")
# 必需输入 name 和 age,否则报错
# TypeError: info() missing 2 required positional arguments: 'age' and 'name'
# info()
info(12,"xiashuo")
# 使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
info(name="xiashuo1212",age=20)
输出:
我的名字是:xiashuo,年龄是:12
我的名字是:xiashuo1212,年龄是:20
默认值
有了默认值,在调用函数的时候,就可以不传入这个参数。注意:带默认值的参数的后面不能存在不带默认值的参数,同时后面的参数可以声明为以 *
或者 **
开头的参数或者 /
或者 *
def test_default_params(age, name="xiashuo.xyz", *dfad, **kw):
print(f"age: {age} name: {name} ")
# 默认参数值
test_default_params(10)
# 指定的参数值
test_default_params(20,'aaaa')
输出:
age: 10 name: xiashuo.xyz
age: 20 name: aaaa
特殊类型的参数
参数名以 * 开头
将传入的多个参数以元组类型保存,前面的参数保证每一个都有值,剩下的都给以 *
开头的参数,同时后面的参数都必须以关键字参数的方式传
# 将传入的多个参数以元组类型保存
def add(*num0):
sum = 0
for i in num0:
sum += i
return sum
print(add(12, 13, 15))
输出:
40
以 *
开头的参数前面,后面还有参数的情况
def test_astrisk_params(a, *b, c):
print(a, b, c)
# a 为 a
# b 为内容为 ('b', 'c', 'd', 'e', 'f', 'f') 的元组
# c 为 && 为关键字参数
print(test_astrisk_params(*"abcdef",c="&&"))
输出:
a ('b', 'c', 'd', 'e', 'f', 'f') &&
可以看到,前面的参数保证每一个都有值,剩下的都给以 *
开头的参数,同时后面的参数都必须以关键字参数的方式传。
参数名以**开头
将传入的多个参数以字典类型保存,注意,传参的时候,键值对中的建不能是数字,以 **
开头的参数后面不能再声明参数,因此,以 **
开头的参数都是参数列表的最后一个参数。
# 将传入的多个参数以字典类型保存
def add_new(**num1):
sum = 0
for i in num1.values():
sum += i
return sum
# key 不能是数字
print(add_new(e=12, f=13, g=15))
输出:
40
同时使用以 *
开头的参数和以 **
开头的参数
def test_double_astrisk_and_astrisk_params(a, *b, c, **d):
print(a, b, c, d)
# c 在 以`*`开头的参数的后面,因此,必须为关键字参数
# a 的值由字符串解包,为 "a"
test_double_astrisk_and_astrisk_params(*"abcde", c="fgh", f=13, g=15)
# a 的值由位置参数直接指定,为 10
test_double_astrisk_and_astrisk_params(10, *"abcde", c="fgh", f=13, g=15)
输出:
a ('b', 'c', 'd', 'e') fgh {'f': 13, 'g': 15}
10 ('a', 'b', 'c', 'd', 'e') fgh {'f': 13, 'g': 15}
参数名为 *
参数为 *
,如果方法的参数列表中单独出现星号 *
,则星号 *
后的参数必须用关键字的格式传入,*
前面的不做限制
这样是为了区分哪些是关键字参数
这和以
*
开头的参数的逻辑是一样的,
简单实践如下:
def test_asterisk(name, age, *, hobby):
return print(f"我的名字是:{name},年龄是:{age},爱好是{hobby}")
# TypeError: test_asterisk() takes 2 positional arguments but 3 were given
# test_asterisk("xiashuo", 45, "钓鱼")
test_asterisk("xiashuo", 45, hobby="钓鱼")
test_asterisk(age=60,name="xiashuo2", hobby="钓鱼")
def keywords(*, name, age):
return print(f"我的名字是:{name},年龄是:{age}")
keywords(name="xiashuo", age=12)
输出:
我的名字是:xiashuo,年龄是:45,爱好是钓鱼
我的名字是:xiashuo2,年龄是:60,爱好是钓鱼
我的名字是:xiashuo,年龄是:12
参数为/
参数为 /
,如果方法的参数列表中单独出现斜杠 /
,则斜杠 /
前面的参数必须用位置参数的格式传入,/
后面的不做要求
简单实践如下:
def positional_arguments(name, age, hobby, /, family):
return print(f"我的名字是:{name},年龄是:{age},爱好是{hobby},我的家庭是{family}")
# TypeError: positional_arguments() got some positional-only arguments passed as keyword arguments: 'name, age'
# positional_arguments("钓鱼",age=12, name="xiashuo",family="虾家")
positional_arguments("xiashuo", 12, "钓鱼", family="虾家")
positional_arguments("xiashuo3", 35, "钓鱼", "虾家")
输出:
我的名字是:xiashuo,年龄是:12,爱好是钓鱼,我的家庭是虾家
我的名字是:xiashuo3,年龄是:35,爱好是钓鱼,我的家庭是虾家
/
和 *
混合使用,显然,/
必须在 *
的前面,也必须在以 *
开头的参数前面
def f(a, b, /, c, d, *, e, f):
print(a, b, c, d, e, f)
f(10, 20, 30, d=40, e=50, f=60)
# f(10, b=20, c=30, d=40, e=50, f=60) # b 不能使用关键字参数的形式
# f(10, 20, 30, 40, 50, f=60) # e 必须使用关键字参数的形式
输出:
10 20 30 40 50 60
值传递和引用传递
在 python 中,类型属于对象,对象有不同类型的区分,变量是没有类型的:
a=[1,2,3]
a="Runoob"
以上代码中,[1,2,3] 是 List 类型,"Runoob" 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是指向 List 类型对象,也可以是指向 String 类型对象。
可更改 (mutable) 与不可更改 (immutable) 对象
在 python 中,strings, tuples, 和 numbers(数字) 是不可更改的对象,而 list,set,dict 等则是可以修改的对象。
-
不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变 a 的值,相当于新生成了 a。
-
可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身 la 没有动,只是其内部的一部分值被修改了。
python 函数的参数传递:
-
不可变类型:类似 C++ 的值传递,如整数、字符串、元组。如 fun(a),传递的只是 a 的值,没有影响 a 对象本身。如果在 fun(a) 内部修改 a 的值,则是新生成一个 a 的对象。
-
可变类型:类似 C++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后 fun 外部的 la 也会受影响
python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。
返回值
return [表达式] 语句用于退出函数,选择性地向调用方返回一个表达式。不带参数值的 return 语句返回 None
简答实践如下
def add_num(*nums):
sum = 0
for i in nums:
sum += i
return sum
sum = add_num(12, 12, 45)
print(sum)
def test_none():
pass
# var 的值是 None ,类型是 NoneType
var = test_none()
print(var,type(var))
def test_none2():
# 也可以直接返回None
return None
输出:
69
None <class 'NoneType'>
变量作用域
变量作用域指的是变量的作用范围(变量在哪里可用,在哪里不可用)
主要分为两类:局部变量和全局变量
-
局部变量:定义在函数体内部的变量,即只在函数体内部生效
-
全局变量:在函数体内、外都能生效的变量,如果有一个数据,在函数 A 和函数 B 中都要使用,那么我们将这个数据存储在一个全局变量里面即可,
在函数体内部,可以直接读全局变量,但是不可以直接写全局变量,想要在函数体内写全局变量,需要在写之前通过 global varibale_name
声明一下,这个变量是全局变量。
说实话,这样不够优雅,有点像打补丁
此外还有一个问题,当我们在函数内部定义了一个跟全局变量同名的变量的时候,这个变量是局部变量还是全局变量呢?
默认是全局变量,可以通过添加 global varibale_name
声明将其声明为全局变量,
同时,我们也可以直接在函数内部通过 global varibale_name
声明一个新的全局变量,然后在别的函数中即可直接使用这个全局变量。
这样我们写代码的时候就更加灵活了,但是也可能会造成问题看下面的实践
简单总结就是,global 关键字的作用是让我们可以在函数内部声明一个变量为全局变量
global 提供了一种,在函数内部,定义全局变量的方式
简单实践如下:
num = 100
def test_1():
# 在函数内部是可以直接使用全局变量的
print(f"test_1:{num}")
def test_2():
# 在函数内部是可以直接使用全局变量的
print(f"test_2:{num}")
test_1()
test_2()
print(f"outer:{num}")
print("------------------------------------------------")
def test_3():
# 下面的语句提示 Unresolved reference 'num' ,说明这个num无法指向全局的num
# num += 50
# 此时这个num不是全局的num,而是 test_3 这个方法内部的局部变量 num
# 对这个局部变量的定义/修改不会影响到全局变量
num = 200
print(f"test_3:{num}")
test_1()
# test_3 中定义的是局部变量,不会影响外部的全局变量
test_3()
# 全局变量并没有变化
print(f"outer:{num}")
print("------------------------------------------------")
def test_4():
# 声明 num 就是全局变量
global num
num = 200
print(f"test_4:{num}")
test_1()
test_4()
# 全局变量同步了变化
print(f"outer:{num}")
print("------------------------------------------------")
# global 提供了一种,在函数内部,定义全局变量的方式,这样我们写代码的时候就更加灵活了
def test_5():
# 声明 age 就是全局变量
global age
age = 600
print(f"test_5:{age}")
def test_6():
# 直接使用 age 这个全局变量
print(f"test_6:{age}")
# 此时 test_5 必须在 test_6 的前面被调用
# 可能会有问题
test_5()
test_6()
print(f"outer:{age}")
输出:
test_1:100
test_2:100
outer:100
------------------------------------------------
test_1:100
test_3:200
outer:100
------------------------------------------------
test_1:100
test_4:200
outer:200
------------------------------------------------
test_5:600
test_6:600
outer:600
在 Java 中,这个问题可以用 this 关键字解决
函数的多返回值
本质上,使用的还是元组可以省略圆括号和 Python 自动解包的特性,具体请看《Python 解包.md》
这个跟 for 循环同时返回多个变量有异曲同工之妙
解包的功能只是把元素从容器中拿出来,但是在赋值语句中,实现了多变量同时赋值,这个特性跟 for 组合实现了 for 循环输出多个值,跟 return 组合实现了方法同时返回多个值
程序之美就是这样,增加一个简单的概念,经过和现有的概念进行组合,可以实现非常复杂的新特性
简单实践如下:
def test_multiple_return_val(x, y, z):
return x, y, z
x, y, z = test_multiple_return_val(100, True, (1,2,3))
print(x, y, z)
输出:
100 True (1, 2, 3)
函数作为参数传递 - 面向函数编程
将一个函数当作一个参数进行传递,是面向函数编程的思想
在 Java 中,lambda 表达式在使用的时候是有类型限制的(函数式接口),即你传入的函数必须符合某种格式,这其实是有所限制的,灵活度不够大,
但是在 Python 中,灵活度非常大,你可以传入任意格式的函数,几乎没有什么限制
函数式编程代表的是一种新的抽象和思考的方式
函数式编程关心数据的映射,命令式编程关心解决问题的步骤
什么是函数式编程思维? - nameoverflow 的回答 - 知乎
https://www.zhihu.com/question/28292740/answer/100284611
什么是函数式编程思维? - 用心阁的回答 - 知乎
https://www.zhihu.com/question/28292740/answer/40336090
在前面的函数学习中,我们一直使用的函数,都是接受数据作为参数传入:
-
数字
-
字符串
-
字典、列表、元组等
其实,我们学习的函数本身,也可以作为参数传入另一个函数内。
简单实践,在下面的例子中,operate 未声明类型,可以接收数据,也可以接收函数(类型为 function
),而且是可以接受任何类型的参数,没有任何限制,这个非常牛逼,
这个跟 Java 不一样,Java 在使用函数类型的参数(lambda 表达式)的时候是需要声明类型的(函数式接口)
但是这样,调用方也可能传入一个完全不相关的不满足要求的函数,在传入这个不满足要求的函数的时候不会报错的,所以我们在执行传入的方法的时候完全不能保证传入的方法是不是我们想要的方法,所以最好加上 try-except
def calculate_num(operate, num0, num1, /):
# 类型为 function
# print(operate,type(operate))
result = -1
try:
result = operate(num0, num1)
except :
print("calculate_num error")
return result
def add(num0, num1):
return num0 + num1
def minus(num0, num1):
return num0 - num1
def not_relevent():
return "------"
# 传入加运算
print(calculate_num(add, 1, 2))
# 传入减运算
print(calculate_num(minus, 10, 2))
# 输出
# calculate_num error
# -1
print(calculate_num(not_relevent, 10, 2))
输出:
3
8
calculate_num error
-1
20
5.0
lambda 匿名函数
Python 使用 lambda 来创建匿名函数。
所谓匿名,意即不再使用 def 语句这样标准的形式定义一个函数。
- lambda 只是一个表达式,函数体比 def 简单很多。
- lambda 的主体是一个表达式,而不是一个代码块。仅仅能在 lambda 表达式中封装有限的逻辑进去。
- lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
- 虽然 lambda 函数看起来只能写一行,却不等同于 C 或 C++ 的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
语法
lambda 函数的语法只包含一个语句,如下:
lambda [arg1 [,arg2,.....argn]]:expression
lambda 函数可以用变量承接,变量类型为 function
这一点跟 Java 是一样的
func_lambda = lambda num0, num1: num0 * num1
# 输出 <function <lambda> at 0x000002A3148E9120> <class 'function'>
print(func_lambda, type(func_lambda))
可以省略参数
no_param = lambda : 1
print(no_param, type(no_param))
在调用上文的 calculate_num
方法的时候,同样可以直接传入 lambda 表达式来作为函数类型的参数
# 输出 20
print(calculate_num(lambda num0, num1: num0 * num1, 10, 2))
# 输出 5.0
print(calculate_num(lambda num0, num1: num0 / num1, 10, 2))
我们可以将匿名函数封装在一个函数内,这样可以使用同样的代码来创建多个匿名函数。
以下实例将匿名函数封装在 myfunc 函数中,通过传入不同的参数来创建不同的匿名函数:
def myfunc(n):
return lambda a : a * n
mydoubler = myfunc(2)
mytripler = myfunc(3)
print(mydoubler(11))
print(mytripler(11))
输出:
22
33